@gpc-cli/core 0.9.75 → 0.9.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3134,7 +3134,9 @@ function formatReviewInsights(analysis) {
3134
3134
  );
3135
3135
  }
3136
3136
  if (analysis.keywords.length > 0) {
3137
- lines.push(` keywords ${analysis.keywords.slice(0, 8).map((k) => k.word).join(", ")}`);
3137
+ lines.push(
3138
+ ` keywords ${analysis.keywords.slice(0, 8).map((k) => k.word).join(", ")}`
3139
+ );
3138
3140
  }
3139
3141
  return lines;
3140
3142
  }
@@ -4823,11 +4825,21 @@ function iconContentType(filePath) {
4823
4825
  }
4824
4826
  async function setAchievementIcon(client, achievementId, filePath) {
4825
4827
  validateResourceId(achievementId, "achievement");
4826
- return client.images.upload(achievementId, "ACHIEVEMENT_ICON", filePath, iconContentType(filePath));
4828
+ return client.images.upload(
4829
+ achievementId,
4830
+ "ACHIEVEMENT_ICON",
4831
+ filePath,
4832
+ iconContentType(filePath)
4833
+ );
4827
4834
  }
4828
4835
  async function setLeaderboardIcon(client, leaderboardId, filePath) {
4829
4836
  validateResourceId(leaderboardId, "leaderboard");
4830
- return client.images.upload(leaderboardId, "LEADERBOARD_ICON", filePath, iconContentType(filePath));
4837
+ return client.images.upload(
4838
+ leaderboardId,
4839
+ "LEADERBOARD_ICON",
4840
+ filePath,
4841
+ iconContentType(filePath)
4842
+ );
4831
4843
  }
4832
4844
  function safeConfigFilename(id) {
4833
4845
  return `${id.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`;
@@ -5930,8 +5942,8 @@ var RESTRICTED_PERMISSIONS = [
5930
5942
  permission: "android.permission.READ_CALL_LOG",
5931
5943
  severity: "critical",
5932
5944
  title: "READ_CALL_LOG requires declaration form",
5933
- message: "READ_CALL_LOG is restricted to default phone/assistant handler apps.",
5934
- suggestion: "Remove READ_CALL_LOG unless your app is a default phone or assistant handler.",
5945
+ message: "READ_CALL_LOG is restricted to default phone/assistant handler apps. As of July 15, 2026, account verification via phone call is no longer an accepted use case for the Call Log permissions.",
5946
+ suggestion: "Remove READ_CALL_LOG unless your app is a default phone or assistant handler. For phone-number verification, use the Digital Credentials API; for one-time codes, use the SMS Retriever API instead of reading the call log.",
5935
5947
  policyUrl: "https://support.google.com/googleplay/android-developer/answer/10208820"
5936
5948
  },
5937
5949
  {
@@ -6863,8 +6875,8 @@ var policyScanner = {
6863
6875
  ruleId: "policy-developer-verification",
6864
6876
  severity: "info",
6865
6877
  title: "Developer verification enforcement (Sep 30, 2026)",
6866
- message: "Google begins enforcing developer verification on September 30, 2026, first in Brazil, Indonesia, Singapore, and Thailand. Apps distributed in those markets need a verified developer identity and a registered package name. Apps using Play App Signing are auto-registered.",
6867
- suggestion: "Run `gpc verify checklist` to review readiness. This is a distribution-market reminder, not an AAB/APK defect.",
6878
+ message: "Google begins enforcing developer verification on September 30, 2026, first in Brazil, Indonesia, Singapore, and Thailand. Apps distributed in those markets need a verified developer identity and a registered package name. Separately, as of July 15, 2026, all apps on Google Play must be registered in Play Console to meet these requirements, and unregistered apps risk removal from Google Play. Apps using Play App Signing are auto-registered.",
6879
+ suggestion: "Run `gpc verify checklist` to review readiness, including registration of every app on your account. This is a distribution-market and account-registration reminder, not an AAB/APK defect.",
6868
6880
  policyUrl: "https://developer.android.com/developer-verification"
6869
6881
  });
6870
6882
  if (perms.has("android.permission.SYSTEM_ALERT_WINDOW")) {
@@ -8913,7 +8925,7 @@ async function sha256File(filePath) {
8913
8925
  }
8914
8926
 
8915
8927
  // src/commands/image-sync.ts
8916
- import { readdir as readdir7 } from "fs/promises";
8928
+ import { readdir as readdir7, stat as stat10 } from "fs/promises";
8917
8929
  import { join as join12, extname as extname5 } from "path";
8918
8930
  import { PlayApiError } from "@gpc-cli/api";
8919
8931
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg"]);
@@ -8943,6 +8955,13 @@ async function scanLanguages(dir) {
8943
8955
  return [];
8944
8956
  }
8945
8957
  }
8958
+ async function dirExists(path) {
8959
+ try {
8960
+ return (await stat10(path)).isDirectory();
8961
+ } catch {
8962
+ return false;
8963
+ }
8964
+ }
8946
8965
  async function syncImages(client, packageName, dir, options) {
8947
8966
  const details = [];
8948
8967
  let uploaded = 0;
@@ -8980,23 +8999,59 @@ async function syncImages(client, packageName, dir, options) {
8980
8999
  const hash = await sha256File(join12(localDir, file));
8981
9000
  localHashes.set(file, hash);
8982
9001
  }
8983
- const localSha256Set = new Set(localHashes.values());
9002
+ const uploadLocal = async (file, reason) => {
9003
+ if (!options?.dryRun) {
9004
+ const filePath = join12(localDir, file);
9005
+ const check = await validateImage(filePath, imageType);
9006
+ if (!check.valid) {
9007
+ throw new GpcError(
9008
+ `Image validation failed for ${language}/${imageType}/${file}: ${check.errors.join("; ")}`,
9009
+ "IMAGE_INVALID",
9010
+ 2,
9011
+ "Check image dimensions, file size, and format."
9012
+ );
9013
+ }
9014
+ await client.images.upload(packageName, edit.id, language, imageType, filePath);
9015
+ }
9016
+ uploaded++;
9017
+ details.push({ language, imageType, file, action: "upload", reason });
9018
+ };
8984
9019
  if (options?.delete) {
8985
- for (const img of remoteImages) {
8986
- if (!localSha256Set.has(img.sha256.toLowerCase())) {
8987
- if (!options?.dryRun) {
8988
- await client.images.delete(packageName, edit.id, language, imageType, img.id);
8989
- }
8990
- deleted++;
9020
+ if (localFiles.length === 0 && remoteImages.length > 0 && !await dirExists(localDir)) {
9021
+ for (const img of remoteImages) {
8991
9022
  details.push({
8992
9023
  language,
8993
9024
  imageType,
8994
9025
  file: img.id,
8995
- action: "delete",
8996
- reason: "not in local"
9026
+ action: "skip",
9027
+ reason: "no local directory; remote left untouched"
8997
9028
  });
8998
9029
  }
9030
+ continue;
8999
9031
  }
9032
+ const localSeq = localFiles.map((file) => localHashes.get(file));
9033
+ const remoteSeq = remoteImages.map((img) => img.sha256.toLowerCase());
9034
+ const inOrder = localSeq.length === remoteSeq.length && localSeq.every((hash, i) => hash === remoteSeq[i]);
9035
+ if (inOrder) {
9036
+ for (const file of localFiles) {
9037
+ skipped++;
9038
+ details.push({ language, imageType, file, action: "skip", reason: "already in order" });
9039
+ }
9040
+ continue;
9041
+ }
9042
+ if (remoteImages.length > 0) {
9043
+ if (!options?.dryRun) {
9044
+ await client.images.deleteAll(packageName, edit.id, language, imageType);
9045
+ }
9046
+ for (const img of remoteImages) {
9047
+ deleted++;
9048
+ details.push({ language, imageType, file: img.id, action: "delete", reason: "reordering" });
9049
+ }
9050
+ }
9051
+ for (const file of localFiles) {
9052
+ await uploadLocal(file, "reordering");
9053
+ }
9054
+ continue;
9000
9055
  }
9001
9056
  for (const file of localFiles) {
9002
9057
  const hash = localHashes.get(file);
@@ -9004,21 +9059,7 @@ async function syncImages(client, packageName, dir, options) {
9004
9059
  skipped++;
9005
9060
  details.push({ language, imageType, file, action: "skip", reason: "sha256 match" });
9006
9061
  } else {
9007
- if (!options?.dryRun) {
9008
- const filePath = join12(localDir, file);
9009
- const check = await validateImage(filePath, imageType);
9010
- if (!check.valid) {
9011
- throw new GpcError(
9012
- `Image validation failed for ${language}/${imageType}/${file}: ${check.errors.join("; ")}`,
9013
- "IMAGE_INVALID",
9014
- 2,
9015
- "Check image dimensions, file size, and format."
9016
- );
9017
- }
9018
- await client.images.upload(packageName, edit.id, language, imageType, filePath);
9019
- }
9020
- uploaded++;
9021
- details.push({ language, imageType, file, action: "upload", reason: "new or changed" });
9062
+ await uploadLocal(file, "new or changed");
9022
9063
  }
9023
9064
  }
9024
9065
  }
@@ -9182,6 +9223,15 @@ function buildChecklist(input) {
9182
9223
  answers
9183
9224
  )
9184
9225
  );
9226
+ items.push(
9227
+ resolveManualStep(
9228
+ "all-apps-registered",
9229
+ "All Play apps registered in Play Console",
9230
+ "As of July 15, 2026, every app on your account must be registered in Play Console to meet Android developer verification requirements. Unregistered apps risk removal from Google Play. Apps using Play App Signing are auto-registered; register any others manually.",
9231
+ PLAY_CONSOLE_SETTINGS,
9232
+ answers
9233
+ )
9234
+ );
9185
9235
  if (input.appAccessible !== void 0) {
9186
9236
  items.push({
9187
9237
  id: "app-accessible",